package utils;


import utils.annotations.Aggregated;
import utils.annotations.Multiple;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Function;

public class SqlFactory {

    private static void handlePara(PreparedStatement p, Object w, int index) throws SQLException {
        if (w instanceof Integer) {
            p.setInt(index, (Integer) w);
        } else if (w instanceof String) {
            p.setString(index, (String) w);
        } else if (w instanceof Long) {
            p.setLong(index, (long) w);
        } else if (w instanceof Double) {
            p.setDouble(index, (double) w);
        } else if (w instanceof Boolean){
            p.setBoolean(index, (boolean) w);
        }
    }

    private static <O> O loadCondition(String sql, Object[] conditions,
                                       BiFunction<Connection, PreparedStatement, O> f)
            throws SQLException {
        Connection connection = JdbcUtil.getConnection();
        var p = connection.prepareStatement(sql);
        if (conditions != null) {
            for (int i = 0; i < conditions.length; i++) {
                handlePara(p, conditions[i], i + 1);
            }
        }
        return f.apply(connection, p);
    }

    public static SqlResult handleQuery(String sql, Object... conditions) throws SQLException {
        return loadCondition(sql, conditions, (con, p) -> {
            try {
                return new SqlResult(p.executeQuery());
            } catch (SQLException e) {
                e.printStackTrace();
                return null;
            }
        });
    }

    public static boolean handleUpdate(String sql, Object... conditions) {
        try {
            return loadCondition(sql, conditions, (con, p) -> {
                try {
                    p.executeUpdate();
                    con.commit();
                    return true;
                } catch (SQLException e) {
                    e.printStackTrace();
                    return false;
                }
            });
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
    }

    public static <O> Object singleSelect(SqlResult resultSet,Class<O> returnType){
        if (resultSet.next()){
            if (returnType.equals(String.class)){
                return resultSet.getString(1);
            }else if (returnType.equals(int.class) || returnType.equals(Integer.class)){
                return resultSet.getInt(1);
            }else if (returnType.equals(double.class) || returnType.equals(Double.class)){
                return resultSet.getDouble(1);
            }else if (returnType.equals(boolean.class) || returnType.equals(Boolean.class)){
                return resultSet.getBoolean(1);
            }else {
                return null;
            }
        }else {
            return null;
        }
    }

    public static <I, O> O handleMultipleResult(SqlResult resultSet, Function<SqlResult, I> map,
                                                Function<Collection<I>, O> transform) {
        var tmp = new ArrayList<I>();
        if (resultSet.next()) {
            do {
                tmp.add(map.apply(resultSet));
            } while (resultSet.next());
        }
        return transform.apply(tmp);
    }

    public static <O> O handleSingleResult(SqlResult resultSet, Function<SqlResult, O> map)
            throws SQLException {
        if (resultSet.next()) {
            return map.apply(resultSet);
        } else {
            return null;
        }
    }


    public static <O> O query(Method method, Function<SqlResult, O> map, Object... args) {
        try {
            if (method.isAnnotationPresent(Aggregated.class)) {
                Aggregated init = method.getAnnotation(Aggregated.class);
                String sql = init.sql();
                return handleSingleResult(handleQuery(sql, args), map);
            } else {
                throw new RuntimeException("need annotation");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static <I, O> O query(Method method, Function<SqlResult, I> map,
                                    Function<Collection<I>, O> transform, Object... args) {
        try {
            if (method.isAnnotationPresent(Multiple.class)) {
                Multiple init = method.getAnnotation(Multiple.class);
                String sql = init.sql();
                return handleMultipleResult(handleQuery(sql, args), map, transform);
            } else {
                throw new RuntimeException("need annotation");
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String getCurrDatabaseUser(){
        Connection con = JdbcUtil.getConnection();
        if (con == null){
            return null;
        }
        PreparedStatement ps;
        try {
            ps = con.prepareStatement("show role");
            var user = ps.executeQuery();
            user.next();
            return user.getString(1);
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }
    }
}
